#!/bin/ksh
#
###########################################################################
# Script to install a Fast Patch
###########################################################################
#
# $Copyright: $
# Copyright (c) 1984-1998
# Sequent Computer Systems, Inc.   All rights reserved.
#  
# This software is furnished under a license and may be used
# only in accordance with the terms of that license and with the
# inclusion of the above copyright notice.   This software may not
# be provided or otherwise made available to, or used by, any
# other person.  No title to or ownership of the software is
# hereby transferred.
#
# installpatch: $Revision: 1.48 $ $Date: 01/06/22 13:10:10 $
#
###########################################################################
# FUNCTIONS
###########################################################################

###################################
# check_debug
#	Returns true of false if debug bit is on
#		p1 - name of bit to check
###################################
function check_debug {
	integer Value

	case $1 in
		(main)				Value=16#80000 ;;
		(command_engine)	Value=16#40000 ;;
		(perform_version)	Value=16#20000 ;;
		(perform_file)		Value=16#10000 ;;
		(perform_libmod)	Value=16#8000 ;;
		(perform_script)	Value=16#4000 ;;
		(version_check)		Value=16#2000 ;;
		(perform_vstop)		Value=16#1000 ;;
		(perform_dir)		Value=16#800 ;;
		(getv)				Value=16#100 ;;
		(getv_state)		Value=16#80 ;;
		(vcheck)			Value=16#40 ;;
		(clean_file)		Value=16#20 ;;
		(dup_attributes)	Value=16#10 ;;
		(commands)			Value=16#1 ;;
		(*)					Value=16#0 ;;
	esac
	if [ $(( Debug & Value )) -eq 0 ]; then
		return 1
	fi
	return 0
}

###################################
# Check that a version is correct
#   p1  product
#
#   Return Codes:
#       0   There was a product match, or FP already installed (gives message)
#       2   No product match
#       -1  No such product
###################################
function version_check {
	# check parameters
	if check_debug version_check; then set -x; fi
	if [ ${#@} -ne 2 ]; then
		print -u2 "ERROR: invalid number of parameters to version_check"
		exit 1
	fi
	typeset Product="$1"

	case ${Product} in
		(bus)           /bin/identify -a ;;
		(*) return -1 ;;
	esac
	return $?
} # end of version_check

###################################
# command_engine
#	Execute commands as found in the BOM
#		p1 - recursion level
###################################
function command_engine {

	###################################
	# p_warn
	#	Display a standard warning message
	#		p1 - text of message
	###################################
	function p_warn {
		print "\nWARNING: ${Command} ${Remain}"
		print "WARNING: $1\n"
	}

	###################################
	# p_error
	#	Display a standard error message
	#		p1 - text of message
	###################################
	function p_error {
		print "\nERROR: ${Command} ${Remain}"
		print "ERROR: $1\n"
	}

	###################################
	# clean_file
	#	either remove or rename a file to make room for new file
	#		p1 - file
	###################################
	function clean_file {

		# check parameters
		if check_debug clean_file; then set -x; fi

		###################################
		# parse options
		###################################
		typeset Quiet=0
		while getopts ':q' Option; do
			case ${Option} in
				(q) Quiet=1
					;;

				(*) if [ ${Option} = '?' ]; then
						print "ERROR: clean_file: unknown option: ${OPTARG}"
					elif [ ${Option} = ':' ]; then
						print "ERROR: clean_file: option ${OPTARG} requires a value"
					fi
					exit 1
					;;
			esac
		done
		shift $((${OPTIND} - 1))		# remove options
		###################################
		if [ ${#*} -ne 1 ]; then
			p_error "clean_file has incorrect number of parameters"
			exit 1
		fi

		typeset File=$1 count

		# short cut exit, file not already there
		if [ ! -a "${File}" ]; then
			return 0
		fi

		# we use -f to prevent asking the user questions, but with -f rm
		# always returns 0 status....thus we check for file existence after
		# the remove
		/bin/rm -f ${File} 2> /dev/null
		if [ ! -a "${File}" ]; then
			if [ $Quiet -eq 0 ]; then
				print "Removed: ${File}"
			fi
			return 0
		fi

		for count in 1 2 3 4 5 6 7 8 9; do
			if /bin/mv -f ${File} ${File}.FPOLD.${count} 2> /dev/null; then
				print "\nFile: ${File}"
				print "Renamed: ${File}.FPOLD.${count}"
				return 0
			fi
		done
		p_error "no space to rename old file: ${File}"
		return 1
	}

	###################################
	# dup_attributes
	#	duplicate attributes of one file to another
	#		p1 - original file
	#		p2 - file that is to have duplicate attributes
	###################################
	function dup_attributes {
		# check parameters
		if check_debug dup_attributes; then set -x; fi
		if [ ${#*} -ne 2 ]; then
			p_error "dup_attributes has incorrect number of parameters"
			exit 1
		fi

		typeset Model=$1 Target=$2
		typeset Lso Lsf Owner Group Access Perm

		if Lso=$(/bin/ls -l ${Model} 2> /dev/null); then
			set -A Lsf -- ${Lso}
			Owner=${Lsf[2]}
			Group=${Lsf[3]}
			if Perm="$(
				print -- "${Lsf[0]}" |
				/usr/bin/nawk '		# original permissions are on stdin
					function proc(start,data) {
						field = substr(data,start,3)	# extract field
						if ( substr(field,3) == "s" )
							field = field "x"			# setuid implies execute
						gsub("-","",field)				# remove all hyphens
						return field
					}
					{ printf "u=%s,g=%s,o=%s",proc(2,$0),proc(5,$0),proc(8,$0) }
				'
			)"; then
				if	/bin/chown ${Owner} ${Target} &&
					/bin/chgrp ${Group} ${Target} &&
					/bin/chmod ${Perm} ${Target}
				then
					return 0
				fi
			fi
		fi
		p_error "cannot duplicate attributes"
		return 1
	}

	###################################
	# perform_version
	#	Check the version and possibly execute a sub BOM
	#		-a - optional switch to use alternative version check script
	#		p1 - product
	#		p2 - version pattern
	#		p3 - optional directory
	###################################
	function perform_version {
		typeset Alt_script=0 Status

		if check_debug perform_version; then set -x; fi

		###################################
		# parse options
		###################################
		while getopts ':a' Option; do
			case ${Option} in
				(a) export Alt_script=1
					;;

				(*) if [ ${Option} = '?' ]; then
						print "ERROR: version: unknown option: ${OPTARG}"
					elif [ ${Option} = ':' ]; then
						print "ERROR: version: option ${OPTARG} requires a value"
					fi
					exit 1
					;;
			esac
		done
		shift $((${OPTIND} - 1))		# remove options
		###################################

		if [[ (${#*} -lt 2) || (${#*} -gt 3) ]]; then
			p_error "version command has incorrect number of parameters"
			return 1
		fi

		typeset Product=$1
		typeset -u Current Version="$2"   # Make upper case

		if [ ${Alt_script} -eq 0 ]; then
			if [ ${Product} = bus ]; then
				# get the version, using the internal mechanism
				Current=$(version_check "${Product}" "${Version}")
				typeset Cstat=$?
				# return values: -1 (255, error), 0 (install) or 2 (no match)
				if [[ ${Cstat} -eq 255 ]]; then
			   		p_error "unknown product type: ${Product}"
			   		return -1
				elif [[ ${Cstat} -eq 2 && ${#*} -ne 3 && "${MODE}" != verify ]]; then
					p_error "product version cannot be located: ${Product}"
					return -1
				fi
			else
				# use the external .getv script
				if [ ! -x ${Getv_Script} ]; then
					p_error "getv script (${Getv_Script}) missing or not executable"
					return 1
				fi
	
				Current=$(ksh ${Getv_Script} "${Product}")
	
				if [ $? -ne 0 ]; then
					p_error "product version cannot be located: ${Product}"
					return -1
				fi
				if [ -z "${Current}" ]; then
					p_error "getv script (${Getv_Script}) did not return version"
					return 1
				fi
			fi
		else
			# use the external .vcheck script
			if [ ! -x ./.vcheck ]; then
				p_error "external version check script (.vcheck) missing or not executable"
				p_error "$(/bin/pwd)/.vcheck"
				return 1
			fi

			if check_debug vcheck; then
				Current=$(ksh -x ./.vcheck "${Product}")
			else
				Current=$(ksh ./.vcheck "${Product}")
			fi
			if [ $? -ne 0 ]; then
				if [ ${MODE} != verify ]; then	# ignore on version check
					p_error "product version cannot be located: ${Product}"
				fi
				return -1
			fi
			if [ -z "${Current}" ]; then
				p_error "external version check script (.vcheck) did not return version"
				return 1
			fi
		fi

		# no directory parameter
		if [ ${#*} -ne 3 ]; then
			if [ -n "${Current}" ]; then
				if eval "[[ ${Current} = ${Version} ]]"; then
					return 0
				fi
			fi
			if [[ "${MODE}" != @(abortinstall|verify) ]]; then
				print "\nERROR: Fast Patch is incompatible with the installed version of ${Product}"
				print "\n		Current version: ${Current}"
				print "	 Version(s) supported: ${Version}\n"
			fi
			return -1
		fi

		# remainder implements version directory version option

		# if versions don't match, continue on
		if [[ "${Current}" != ${Version} ]]; then
			return 0
		fi

		typeset Sub_dir="$3"
		if [[ "${Sub_dir}" = @(/|../)* ]]; then
			p_error "sub-directory cannot start with / or ../"
			return 1
		fi

		# execute the directory
		if [ -d "${Sub_dir}" ]; then
			Version_undone=0
			(	cd ${Sub_dir}
				command_engine ${Recursion_level}
			)
			return $?
		else
			p_error "sub-directory is missing"
			return 1
		fi
	}

	###################################
	# perform_subdir
	#	Execute a sub BOM
	#		p1 - directory
	###################################
	function perform_subdir {
		if check_debug perform_version; then set -x; fi

		if [ ${#*} -ne 1 ]; then
			p_error "subdir command has incorrect number of parameters"
			return 1
		fi

		# check if executable
		if [ ! -e "$1/BOM" ]; then
			p_error "Already exists: $1"
			return 1
		fi

		typeset Sub_dir="$1"
		if [[ "${Sub_dir}" = @(/|../)* ]]; then
			p_error "sub-directory cannot start with / or ../"
			return 1
		fi

		# execute the directory
		if [ -d "${Sub_dir}" ]; then
			Version_undone=0
			(	cd ${Sub_dir}
				command_engine ${Recursion_level}
			)
			return $?
		else
			p_error "sub-directory is missing"
			return 1
		fi
	}

	###################################
	# perform_dir
	#	Check that the directory exists and has the correct attributes
	#		-f - force attributes to be reset
	#		p1 - owner
	#		p2 - group
	#		p3 - access
	#		p4 - directory name
	###################################
	function perform_dir {
		typeset Force_att=0

		if check_debug perform_dir; then set -x; fi

		###################################
		# parse options
		###################################
		while getopts ':f' Option; do
			case ${Option} in
				(f) export Force_att=1
					;;

				(*) if [ ${Option} = '?' ]; then
						print "ERROR: dir: unknown option: ${OPTARG}"
					elif [ ${Option} = ':' ]; then
						print "ERROR: dir: option ${OPTARG} requires a value"
					fi
					exit 1
					;;
			esac
		done
		shift $((${OPTIND} - 1))		# remove options
		###################################

		if [ ${#*} -ne 4 ]; then
			p_error "dir command has incorrect number of parameters"
			return 1
		fi

		if [ ${Recursion_level} -le 1 ]; then
			p_error "dir command cannot be used in first level BOM"
			return 1
		fi

		if [ ${MODE} = verify ]; then
			# ignore on version check
			return 0
		fi

		typeset Dest_dir=$4						# full destination directory
		typeset Parent_dir="$(/bin/dirname ${Dest_dir})"	# directory

		# ensure that installation directory exists
		if [ ! -d "${Parent_dir}" ]; then
			p_error "destination directory does not exist: ${Parent_dir}"
			return 1
		fi

		case ${MODE} in
			(install)
				print ""

				if [ ! -a "${Dest_dir}" ]; then
					# nothing there, make a directory
					if	/bin/mkdir ${Dest_dir} &&
						/bin/chown $1 ${Dest_dir} &&
						/bin/chgrp $2 ${Dest_dir} &&
						/bin/chmod $3 ${Dest_dir}
					then
						print "Directory created: ${Dest_dir}"
					else
						p_error "Could not create directory"
						return 1
					fi
				elif [ -d "${Dest_dir}" ]; then
					# directory already exists
					if [ ${Force_att} -eq 1 ]; then
						if	/bin/chown $1 ${Dest_dir} &&
							/bin/chgrp $2 ${Dest_dir} &&
							/bin/chmod $3 ${Dest_dir}
						then
							print "Directory attributes set: ${Dest_dir}"
						else
							p_error "Could not set attributes: ${Dest_dir}"
							return 1
						fi
					else
						print "Directory already exists: ${Dest_dir}"
					fi
				else
					# it is not a directory
					/bin/ls -ld ${Dest_dir}
					p_error "Could not create directory, file already exists"
					return 1
				fi
				;;

			(abortinstall)
				# do nothing, leave directory in existance
				;;

			(deinstall)
				# do nothing, leave directory in existance
				;;

			(*)
				p_error "bad MODE=${MODE} in $0"
				exit 1
				;;
		esac
		return 0
	}

	###################################
	# perform_file
	#	Install a file
	#		p1 - owner
	#		p2 - group
	#		p3 - access
	#		p4 - pathname
	###################################
	function perform_file {
		typeset Zoption=0

		if check_debug perform_file; then set -x; fi

		###################################
		# parse options
		###################################
		while getopts ':z' Option; do
			case ${Option} in
				(z) Zoption=1
					;;

				(*) if [ ${Option} = '?' ]; then
						print "ERROR: file: unknown option: ${OPTARG}"
					elif [ ${Option} = ':' ]; then
						print "ERROR: file: option ${OPTARG} requires a value"
					fi
					exit 1
					;;
			esac
		done
		shift $((${OPTIND} - 1))		# remove options
		###################################
		if [ ${#*} -ne 4 ]; then
			p_error "file command has incorrect number of parameters"
			return 1
		fi

		if [ ${Recursion_level} -le 1 ]; then
			p_error "file command cannot be used in first level BOM"
			return 1
		fi

		if [ ${MODE} = verify ]; then
			# ignore on version check
			return 0
		fi

		eval typeset Dest_path=$4				# full destination pathname
		typeset Dest_dir="$(/bin/dirname ${Dest_path})"	# destination directory
		typeset Dest_file="${Dest_path##*/}"	# file name of destination

		# ensure that installation directory exists
		if [ ! -d "${Dest_dir}" ]; then
			p_error "destination directory does not exist: ${Dest_dir}"
			return 1
		fi

		case ${MODE} in
			(install)
				print ""
				# make a save directory if one is not already there
				if [ ! -d save_old ]; then
					if /bin/mkdir save_old; then
						/bin/chmod go= save_old
					else
						p_error "save_old directory could not be created"
						return 1
					fi
				fi

				# build the copy command
				if [ ${Zoption} -eq 0 ]; then
					Cmd="/bin/cat ${Dest_file} > ${Dest_path}.FPNEW"
					typeset Fcheck=${Dest_file}
				else
					Cmd="/usr/bin/uncompress -c ${Dest_file}.Z > ${Dest_path}.FPNEW"
					typeset Fcheck=${Dest_file}.Z
				fi

				if [ ! -a "${Fcheck}" ]; then
					p_error "File is missing from Fast Patch"
					p_error "$(/bin/pwd)/${Fcheck}"
					return 1
				fi

				if [ -a "${Dest_path}" ]; then
					# save the original file if we haven't already saved it
					if [ -a save_old/${Dest_file} ]; then
						print "Already saved: ${Dest_path}"
					else
						if	/bin/cp ${Dest_path} save_old &&
							dup_attributes ${Dest_path} save_old/${Dest_file}
						then
							print "Saved: ${Dest_path}"
						else
							p_error "Could not save, check disk space"
							/bin/rm -f save_old/${Dest_file}
							return 1
						fi
					fi
					/bin/ln ${Dest_path} ${Dest_path}.FPHL
				else
					print "Not present: ${Dest_path}"
					/bin/touch save_old/${Dest_file}.NP
				fi

				if	eval ${Cmd} &&
					/bin/chown $1 ${Dest_path}.FPNEW &&
					/bin/chgrp $2 ${Dest_path}.FPNEW &&
					/bin/chmod $3 ${Dest_path}.FPNEW
				then
					if /bin/mv -f ${Dest_path}.FPNEW ${Dest_path}; then
						print "Installed: ${Dest_path}"
						# don't try to delete original file if there wasn't one
						if [ -a "${Dest_path}.FPHL" ]; then
							clean_file ${Dest_path}.FPHL
						fi
					else
						p_error "Could not move into place"
						/bin/rm -f ${Dest_path}.FPNEW ${Dest_path}.FPHL
						return 1
					fi
				else
					p_error "Could not install, check disk space"
					/bin/rm -f ${Dest_path}.FPNEW ${Dest_path}.FPHL
					return 1
				fi
				;;

			(abortinstall)
				if [ -a "save_old/${Dest_file}" ]; then
					if	/bin/cp save_old/${Dest_file} ${Dest_path}.FPNEW &&
						dup_attributes save_old/${Dest_file} ${Dest_path}.FPNEW &&
						/bin/ln ${Dest_path} ${Dest_path}.FPHL &&
						/bin/mv ${Dest_path}.FPNEW ${Dest_path}
					then
						print "Restored: ${Dest_path}"
						clean_file ${Dest_path}.FPHL &&
						/bin/rm -f save_old/${Dest_file}
					else
						p_error "cannot restore: ${Dest_path}"
						return 1
					fi
				elif [ -a save_old/${Dest_file}.NP ]; then
					if clean_file ${Dest_path}; then
						print "File was not present before this Fast Patch"
						/bin/rm -f save_old/${Dest_file}.NP
					fi
				fi
				;;

			(deinstall)
				if [ -a "save_old/${Dest_file}" ]; then
					if	/bin/cp save_old/${Dest_file} ${Dest_path}.FPNEW &&
						dup_attributes save_old/${Dest_file} ${Dest_path}.FPNEW &&
						/bin/ln ${Dest_path} ${Dest_path}.FPHL &&
						/bin/mv ${Dest_path}.FPNEW ${Dest_path}
					then
						print "Deinstalled: ${Dest_path}"
						clean_file ${Dest_path}.FPHL &&
						/bin/rm -f save_old/${Dest_file}
					else
						p_error "cannot deinstall: ${Dest_path}"
						return 1
					fi
				elif [ -a save_old/${Dest_file}.NP ]; then
					if clean_file ${Dest_path}; then
						print "File was not present before this Fast Patch"
						/bin/rm -f save_old/${Dest_file}.NP
					fi
				else
					p_error "cannot deinstall, not saved"
					return 1
				fi
				;;

			(*)
				p_error "bad MODE=${MODE} in $0"
				exit 1
				;;
		esac
		return 0
	}

	###################################
	# perform_libmod
	#	Install a module in a library
	#		p1 - library
	#		p2-n - modules
	###################################
	function perform_libmod {
		typeset Coff=""

		if check_debug perform_libmod; then set -x; fi

		###################################
		# parse options
		###################################
		while getopts ':c:' Option; do
			case ${Option} in
				(c) if [ "${OPTARG}" != "off" ]; then
						print "ERROR: libmod: unknown option: ${OPTARG}"
						exit 1
					fi
					Coff="-coff"
					;;

				(*) if [ ${Option} = '?' ]; then
						print "ERROR: libmod: unknown option: ${OPTARG}"
					elif [ ${Option} = ':' ]; then
						print "ERROR: libmod: option ${OPTARG} requires a value"
					fi
					exit 1
					;;
			esac
		done
		shift $((${OPTIND} - 1))		# remove options
		###################################

		if [ ${#*} -lt 2 ]; then
			p_error "libmod command has incorrect number of parameters"
			return 1
		fi

		if [ ${Recursion_level} -le 1 ]; then
			p_error "libmod command cannot be used in first level BOM"
			return 1
		fi

		if [ ${MODE} = verify ]; then
			# ignore on version check
			return 0
		fi

		typeset Library="$1" Module List
		shift

		# ensure that installation library exists
		if [[ ! -w "${Library}" && "${MODE}" != verify ]]; then
			p_error "installation library does not exist: ${Library}"
			return 1
		fi

		case ${MODE} in
			(install)
				# make a save directory if one is not already there
				if [ ! -d save_old ]; then
					if /bin/mkdir save_old; then
						/bin/chmod go= save_old
					else
						p_error "save_old directory could not be created"
						return 1
					fi
				fi

				print "\nLibrary: ${Library}"

				# Make sure we have alll the modules
				for Module in $*; do
					if [ ! -s "${Module}" ]; then
						p_error "Module is missing from Fast Patch"
						p_error "$(/bin/pwd)/${Module}"
						return 1
					fi
				done

				# save all modules being changed
				Tempf=/tmp/FPin.$$
				for Module in $*; do
					if [ -s "save_old/${Module}" ]; then
						print "  Already saved: ${Module}"
					elif [ -a save_old/${Module}.NP ]; then
						print "  Already saved: ${Module}"
					elif (	cd save_old
							ar ${Coff} x ${Library} ${Module} 2> ${Tempf}); then
						print "  Module saved: ${Module}"
					elif /bin/grep '^ar: .* not found$' ${Tempf} > /dev/null
					then
						print "  Module not in library: ${Module}"
						touch save_old/${Module}.NP
					else
						/bin/cat ${Tempf}
						/bin/rm -f ${Tempf}
						return 1
					fi
				done
				/bin/rm -f ${Tempf}

				if ar ${Coff} r ${Library} $*; then
					print "  Modules replaced: $*"
					return 0
				fi
				return 1
				;;

			(abortinstall)
				print "\nLibrary: ${Library}"
				List="" Dlist=""
				for Module in $*; do
					if [ -s "save_old/${Module}" ]; then
						List="${List} ${Module}"
					elif [ -a "save_old/${Module}.NP" ]; then
						Dlist="${Dlist} ${Module}"
					fi
				done
				if (cd save_old; ar ${Coff} r ${Library} ${List}); then
					print "  Modules restored: ${List}"
				fi
				if (cd save_old; ar ${Coff} d ${Library} ${Dlist}); then
					print "  Modules removed: ${Dlist}"
				fi
				;;

			(deinstall)
				print "\nLibrary: ${Library}"
				List="" Dlist=""
				for Module in $*; do
					if [ -s "save_old/${Module}" ]; then
						List="${List} ${Module}"
					elif [ -a save_old/${Module}.NP ]; then
						Dlist="${Dlist} ${Module}"
					fi
				done
				if (cd save_old; ar ${Coff} r ${Library} ${List}); then
					print "  Modules deinstalled: ${List}"
				fi
				if (cd save_old; ar ${Coff} d ${Library} ${Dlist}); then
					print "  Modules removed: ${Dlist}"
				fi
				;;

			(*)
				p_error "bad MODE=${MODE} in $0"
				exit 1
				;;
		esac
	}

	###################################
	# perform_script
	#	Execute a script
	#		p1 - script name
	###################################
	function perform_script {
		# check parameters
		if check_debug perform_script; then set -x; fi

		if [ ${MODE} = verify ]; then
			# ignore on version check
			return 0
		fi

		# check if executable
		if [ ! -x "$1" ]; then
			p_error "Cannot execute: $1"
			return 1
		fi

		# the eval helps with quoted parameters, however multiple blanks
		# will be squashed to single blanks
		eval ./$@
		return $?
	}

	###################################
	# perform_vstop
	#	If no version match by now, exit with -1
	#	Needs to be a function to make error reporting work
	###################################
	function perform_vstop {
		# check parameters
		if check_debug perform_vstop; then set -x; fi
		if [ ${#*} -ne 0 ]; then
			p_error "vstop command has incorrect number of parameters"
			return 1
		fi

		if (( Version_undone )); then
			if [ ${MODE} != verify ] ; then
				print "\nERROR: No versions match current system\n"
			fi
			return -1
		fi
		return 0
	}

	###################################
	# command_engine main
	###################################

	if check_debug command_engine; then set -x; fi
	typeset Command Remain
	let Recursion_level=$1+1

	if [ ${Recursion_level} -le 1 ]; then
		Getv_Script="$(/bin/pwd)/.getv"
	fi

	if [ ! -r BOM ]; then
		print "\nERROR: BOM is missing or not readable, cannot continue"
		print "ERROR: $(/bin/pwd)/BOM"
		exit 1
	fi

	exec 3< BOM		# use this so that stdin still works
	while read -u3 Command Remain; do
		if [ -z "${Command}" ]; then			# skip blank lines
			continue
		fi
		if [[ "${Command}" = "#"* ]]; then		# skip comments
			continue
		fi
		if check_debug commands; then
			print "COMMAND: Command=$Command  Remain=\"$Remain\""
		fi
		set -f		# turn file name matching off
		case ${Command} in
			(version)	# check the version of a product and/or use different
				perform_version ${Remain}
				;;

			(subdir)	# unconditionally execute a subdirectory's BOM
				perform_subdir ${Remain}
				;;

			(vstop)		# stop if no version matches
				perform_vstop ${Remain}
				;;

			(dir)		# create directory if needed
				perform_dir ${Remain}
				;;

			(file)		# install a program/file/entire library
				perform_file ${Remain}
				;;

			(libmod)	# install a library module
				perform_libmod ${Remain}
				;;

			(script)	# execute the given script, special stuff
				perform_script ${Remain}
				;;

			(msg)	# print a message to the terminal
				if [ ${MODE} != verify ]; then
					Remain="${Remain#\"}"
					Remain="${Remain%\"}"
					print "${Remain}"
				fi
				;;

			(imsg)	# print a message to the terminal on install
				if [ ${MODE} = install ]; then
					Remain="${Remain#\"}"
					Remain="${Remain%\"}"
					print "${Remain}"
				fi
				;;

			(amsg)	# print a message to the terminal on abortinstall
				if [ ${MODE} = abortinstall ]; then
					Remain="${Remain#\"}"
					Remain="${Remain%\"}"
					print "${Remain}"
				fi
				;;

			(dmsg)	# print a message to the terminal on de-install
				if [ ${MODE} = deinstall ]; then
					Remain="${Remain#\"}"
					Remain="${Remain%\"}"
					print "${Remain}"
				fi
				;;

			(title)	# give the Fast Patch a title
				Title=" (${Remain})"
				;;

			(*)		# unknown command
				p_warn "Unknown command ignored"
				;;
		esac
		Status=$?
		set +f		# turn name matching back on
		if [ ${Status} -ne 0 ]; then
			return ${Status}
		fi
	done

	# enforce a vstop if we hit EOF on first level BOM
	# this makes the vstop command redundant
	if [ ${Recursion_level} -eq 1 ]; then
		perform_vstop
		Status=$?
		return $Status
	fi
	return 0
}

##############################################################################
# MAIN MAIN MAIN MAIN MAIN MAIN MAIN MAIN MAIN MAIN MAIN MAIN MAIN MAIN MAIN #
##############################################################################

typeset -x PATH=/bin:/usr/bin:/etc
typeset -x CWD=$(/bin/pwd)
typeset -x Versionlog=/etc/versionlog
typeset -x MODE=install
typeset Title=""
integer Debug=0 Version_undone=1 Force=0
unset ENV FPATH OBJ_FORM

# make date consistent by unsetting all the customisable time stuff
unset LC_TIME LANG LANGUAGE CFTIME;

###################################
# parse options
###################################
while getopts ':dFD:V:' Option; do
	case ${Option} in
		(d) MODE=deinstall
			;;

		(F) export Force=1
			;;

		(D) integer Debug=${OPTARG}
			if check_debug main; then set -x; fi
			;;

		(V) MODE=verify
			Versionlog=${OPTARG}
			;;

		(*) if [ ${Option} = '?' ]; then
				print "ERROR: installpatch: unknown option: ${OPTARG}"
			elif [ ${Option} = ':' ]; then
				print "ERROR: installpatch: option ${OPTARG} requires a value"
			fi
			exit 1
			;;
	esac
done
shift $((${OPTIND} - 1))		# remove options

# check for extraneous parameters
if (( $# > 0 )); then
	print "ERROR: installpatch: too many parameters"
	exit 1
fi

###################################
# Check access and permissions
###################################

if [ ! -r ${Versionlog} ]; then
	print "\nERROR: ${Versionlog} is not readable, cannot continue\n"
	exit 1
fi

if [ ${MODE} = verify ]; then		# check the given version log
	command_engine 0
	exit ${Status}
fi

###################################
# Install by user
#
# determine Fast Patch number and CRN from current path
###################################
typeset FPCRN=${CWD##*/FP\#}
export FP=${FPCRN%%.*} CRN=${FPCRN#*.}
if [[ "${FPCRN}" != [1-9]+([0-9]).[1-9]+([0-9])@(.+([0-9])|+([0-9])) ]]; then
	print "\nERROR: ${CWD}"
	print "ERROR: not a Fast Patch directory, cannot continue\n"
	exit 1
fi

###################################
# ensure we are running as root
###################################
# Ensure that we are root (/usr/bin/logname not it v1.2.4?)
User=$(/usr/bin/id | sed 's/).*$//;s/.*(//')
if [ ${User} != "root" ]; then
	print "\nERROR: Current user is: ${User}"
	print "ERROR: This Fast Patch must be installed from the account root"
	print "ERROR: Please su or login as root and try again\n"
	exit 1
fi

if [ ! -w ${Versionlog} ]; then
	print "\nERROR: ${Versionlog} is missing or not writable, cannot continue\n"
	exit 1
fi

###################################
#Stamp /etc/versionlog and invoke the install engine
###################################
(
	print "Commencing patch install: $(/bin/date)"
	if command_engine 0; then
		Result=" complete"
	else
		if [ ${MODE} = deinstall ]; then 
			print "*******************************"
			print "ERROR: DEINSTALL IS BEING ABORTED"
			print "Customer support needs to manually verify the state of this system"
			print "*******************************"
		else
			print "*******************************"
			print "ERROR: INSTALL IS BEING ABORTED"
			print "*******************************"
			MODE=abortinstall
			command_engine 0
		fi
		Result=" ABORTED"
	fi
	print "Ending patch install: $(/bin/date)"
	print ""
	print "********************************************************************"
	print "Do NOT remove this directory - it contains vital information that"
	print "may be needed later to deinstall this patch in the event of a problem"
	print *"*******************************************************************"
	print ""

	print "$(/bin/date) - Fastpatch FP#${FPCRN}  ${MODE}${Result}${Title}" |
		tee -a ${Versionlog}

) 2>&1 | tee -a LOG
exit ${Status}
